﻿using System;
using System.Collections.Generic;
using System.Text;
using System.IO;
using System.Globalization;

namespace Yuyi.PluginForJS.JavaScriptLexer
{
    public class Lexer
    {
        private StringBuilder sb = new StringBuilder();

        TextReader reader;

        private Token currentToken;

        public Token CurrentToken { get { return currentToken; } }
        private int line;
        private int col;

        private StringBuilder originalValue = new StringBuilder();

        public int Line
        {
            get { return line; }
        }

        public int Col
        {
            get { return col; }
        }

        public Lexer(TextReader reader)
        {
            this.reader = reader;

            currentToken = Next();
            Token peekToken = currentToken;
            while ((peekToken.next = Next()).kind != Tokens.EOF)
                peekToken = peekToken.next;

            Complete();
        }

        public void GetNextToken()
        {
            currentToken = currentToken.next;
        }
        /// <summary>
        /// 读取下一个字符，并且推进一列
        /// </summary>
        /// <returns></returns>
        private int ReaderRead()
        {
            ++col;
            return reader.Read();
        }
        /// <summary>
        /// 读取下一个字符，但指针并不推进
        /// </summary>
        /// <returns></returns>
        private int ReaderPeek()
        {
            return reader.Peek();
        }
        /// <summary>
        /// 得到一个Token，不是使用状态机的实现
        /// </summary>
        /// <returns></returns>
        private Token Next()
        {
            int nextChar;
            char ch;
            while ((nextChar = ReaderRead()) != -1)
            {
                Token token;

                switch (nextChar)
                {
                    case ' ':
                    case '\t':
                        continue;
                    case '\r':
                        continue;
                    case '\n':
                        col = 1;
                        //换行
                        return new Token(Tokens.EndOfLine, col, line++);
                    case '/':
                        int peek = ReaderPeek();
                        if (peek == '/' || peek == '*')
                        {
                            ReadComment();
                            continue;
                        }
                        else
                        {
                            token = ReadOperator('/');
                        }
                        break;
                    case '"':
                        token = ReadString();
                        break;
                    case '\'':
                        token = ReadChar();
                        break;
                    default:
                        ch = (char)nextChar;
                        if (Char.IsLetter(ch) || ch == '_' || ch == '$')
                        {
                            int x = Col - 1;
                            int y = Line;
                            bool canBeKeyword;
                            string s = ReadIdent(ch, out canBeKeyword);
                            if (canBeKeyword)
                            {
                                int keyWordToken = Keywords.GetToken(s);
                                if (keyWordToken >= 0)
                                {
                                    return new Token(keyWordToken, x, y, s);
                                }
                            }
                            return new Token(Tokens.Identifier, x, y, s);
                        }
                        else if (Char.IsDigit(ch))
                        {
                            token = ReadDigit(ch, Col - 1);
                        }
                        else
                        {
                            token = ReadOperator(ch);
                        }
                        break;
                }
                if (token != null)
                {
                    return token;
                }
            }

            return new Token(Tokens.EOF, Col, Line, String.Empty);
        }
        /// <summary>
        /// 读取运算符
        /// </summary>
        /// <param name="ch"></param>
        /// <returns></returns>
        Token ReadOperator(char ch)
        {
            int x = Col - 1;
            int y = Line;
            switch (ch)
            {
                case '+':
                    switch (ReaderPeek())
                    {
                        case '+':
                            ReaderRead();
                            return new Token(Tokens.Increment, x, y);
                        case '=':
                            ReaderRead();
                            return new Token(Tokens.PlusAssign, x, y);
                    }
                    return new Token(Tokens.Plus, x, y);
                case '-':
                    switch (ReaderPeek())
                    {
                        case '-':
                            ReaderRead();
                            return new Token(Tokens.Decrement, x, y);
                        case '=':
                            ReaderRead();
                            return new Token(Tokens.MinusAssign, x, y);
                    }
                    return new Token(Tokens.Minus, x, y);
                case '*':
                    switch (ReaderPeek())
                    {
                        case '=':
                            ReaderRead();
                            return new Token(Tokens.TimesAssign, x, y);
                        default:
                            break;
                    }
                    return new Token(Tokens.Times, x, y);
                case '/':
                    if (ReaderPeek() == '=')
                    {
                        ReaderRead();
                        return new Token(Tokens.DivAssign, x, y);
                    }
                    return new Token(Tokens.Div, x, y);
                case '%':
                    if (ReaderPeek() == '=')
                    {
                        ReaderRead();
                        return new Token(Tokens.ModAssign, x, y);
                    }
                    return new Token(Tokens.Mod, x, y);
                case '&':
                    switch (ReaderPeek())
                    {
                        case '&':
                            ReaderRead();
                            return new Token(Tokens.LogicalAnd, x, y);
                        case '=':
                            ReaderRead();
                            return new Token(Tokens.BitwiseAndAssign, x, y);
                    }
                    return new Token(Tokens.BitwiseAnd, x, y);
                case '|':
                    switch (ReaderPeek())
                    {
                        case '|':
                            ReaderRead();
                            return new Token(Tokens.LogicalOr, x, y);
                        case '=':
                            ReaderRead();
                            return new Token(Tokens.BitwiseOrAssign, x, y);
                    }
                    return new Token(Tokens.BitwiseOr, x, y);
                case '^':
                    if (ReaderPeek() == '=')
                    {
                        ReaderRead();
                        return new Token(Tokens.XorAssign, x, y);
                    }
                    return new Token(Tokens.Xor, x, y);
                case '!':
                    if (ReaderPeek() == '=')
                    {
                        ReaderRead();
                        return new Token(Tokens.NotEqual, x, y);
                    }
                    return new Token(Tokens.Not, x, y);
                case '~':
                    return new Token(Tokens.BitwiseComplement, x, y);
                case '=':
                    switch (ReaderPeek())
                    {
                        case '=':
                            ReaderRead();
                            return new Token(Tokens.Equal, x, y);
                    }
                    return new Token(Tokens.Assign, x, y);
                case '<':
                    switch (ReaderPeek())
                    {
                        case '<':
                            ReaderRead();
                            if (ReaderPeek() == '=')
                            {
                                ReaderRead();
                                return new Token(Tokens.ShiftLeftAssign, x, y);
                            }
                            return new Token(Tokens.ShiftLeft, x, y);
                        case '=':
                            ReaderRead();
                            return new Token(Tokens.LessEqual, x, y);
                    }
                    return new Token(Tokens.LessThan, x, y);
                case '>':
                    if (ReaderPeek() == '=')
                    {
                        ReaderRead();
                        return new Token(Tokens.GreaterEqual, x, y);
                    }
                    return new Token(Tokens.GreaterThan, x, y);
                case '?':
                    return new Token(Tokens.Question, x, y);
                case ';':
                    return new Token(Tokens.Semicolon, x, y);
                case ':':
                    return new Token(Tokens.Colon, x, y);
                case ',':
                    return new Token(Tokens.Comma, x, y);
                case '.':
                    int tmp = ReaderPeek();
                    if (tmp > 0 && Char.IsDigit((char)tmp))
                    {
                        return ReadDigit('.', Col - 1);
                    }
                    return new Token(Tokens.Dot, x, y);
                case ')':
                    return new Token(Tokens.CloseParenthesis, x, y);
                case '(':
                    return new Token(Tokens.OpenParenthesis, x, y);
                case ']':
                    return new Token(Tokens.CloseSquareBracket, x, y);
                case '[':
                    return new Token(Tokens.OpenSquareBracket, x, y);
                case '}':
                    return new Token(Tokens.CloseCurlyBrace, x, y);
                case '{':
                    return new Token(Tokens.OpenCurlyBrace, x, y);
                default:
                    return null;
            }
        }
        /// <summary>
        /// 读取注释
        /// </summary>
        void ReadComment()
        {
            if ( ReaderRead()== '*')
                ReadMultiLineComment();
            else
                ReadSingleLineComment();
        }
        /// <summary>
        /// 读取单行注释
        /// </summary>
        private void ReadSingleLineComment()
        {
            int nextChar;
            while ((nextChar = reader.Read()) != -1)
            {
                if (HandleLineEnd((char)nextChar))
                {
                    break;
                }
            }
        }
        /// <summary>
        /// 读取多行注释
        /// </summary>
        void ReadMultiLineComment()
        {
            int nextChar;
            while ((nextChar = ReaderRead()) != -1)
            {
                char ch = (char)nextChar;
                if (ch == '*' && ReaderPeek() == '/')
                {
                    ReaderRead();
                    return;
                }
                else
                {
                    HandleLineEnd(ch);
                }
            }
        }

        const int MAX_IDENTIFIER_LENGTH = 512;
        char[] identBuffer = new char[MAX_IDENTIFIER_LENGTH];
        /// <summary>
        /// 读取标识符
        /// </summary>
        /// <param name="ch">传进来的字符</param>
        /// <param name="canBeKeyword">可能是关键字</param>
        /// <returns></returns>
        string ReadIdent(char ch, out bool canBeKeyword)
        {
            int peek;
            int curPos = 0;
            canBeKeyword = true;
            while (true)
            {
                if (curPos < MAX_IDENTIFIER_LENGTH)
                {
                    identBuffer[curPos++] = ch;
                }
                else
                {
                    while (IsIdentifierPart(ReaderPeek()))
                    {
                        ReaderRead();
                    }
                    break;
                }
                peek = ReaderPeek();
                if (IsIdentifierPart(peek) || peek == '\\')
                {
                    ch = (char)ReaderRead();
                }
                else
                {
                    break;
                }
            }
            return new String(identBuffer, 0, curPos);
        }
        /// <summary>
        /// 读取数字
        /// </summary>
        /// <param name="ch"></param>
        /// <param name="x"></param>
        /// <returns></returns>
        Token ReadDigit(char ch, int x)
        {
            unchecked
            {
                int y = Line;
                sb.Length = 0;
                sb.Append(ch);
                string prefix = null;
                string suffix = null;

                bool ishex = false;
                bool isunsigned = false;
                bool islong = false;
                bool isfloat = false;
                bool isdouble = false;
                bool isdecimal = false;

                char peek = (char)ReaderPeek();

                if (ch == '.')
                {
                    isdouble = true;

                    while (Char.IsDigit((char)ReaderPeek()))
                    {
                        sb.Append((char)ReaderRead());
                    }
                    peek = (char)ReaderPeek();
                }
                else if (ch == '0' && (peek == 'x' || peek == 'X'))
                {
                    ReaderRead();
                    sb.Length = 0;
                    while (IsHex((char)ReaderPeek()))
                    {
                        sb.Append((char)ReaderRead());
                    }
                    if (sb.Length == 0)
                    {
                        sb.Append('0'); 
                    }
                    ishex = true;
                    prefix = "0x";
                    peek = (char)ReaderPeek();
                }
                else
                {
                    while (Char.IsDigit((char)ReaderPeek()))
                    {
                        sb.Append((char)ReaderRead());
                    }
                    peek = (char)ReaderPeek();
                }

                Token nextToken = null;
                if (peek == '.')
                {
                    ReaderRead();
                    peek = (char)ReaderPeek();
                    if (!Char.IsDigit(peek))
                    {
                        nextToken = new Token(Tokens.Dot, Col - 1, Line);
                        peek = '.';
                    }
                    else
                    {
                        isdouble = true;
                        sb.Append('.');
                        while (Char.IsDigit((char)ReaderPeek()))
                        {
                            sb.Append((char)ReaderRead());
                        }
                        peek = (char)ReaderPeek();
                    }
                }
                if (peek == 'e' || peek == 'E')
                {
                    isdouble = true;
                    sb.Append((char)ReaderRead());
                    peek = (char)ReaderPeek();
                    if (peek == '-' || peek == '+')
                    {
                        sb.Append((char)ReaderRead());
                    }
                    while (Char.IsDigit((char)ReaderPeek()))
                    {
                        sb.Append((char)ReaderRead());
                    }
                    isunsigned = true;
                    peek = (char)ReaderPeek();
                }

                if (peek == 'f' || peek == 'F')
                {
                    ReaderRead();
                    suffix = "f";
                    isfloat = true;
                }
                else if (peek == 'd' || peek == 'D')
                {
                    ReaderRead();
                    suffix = "d";
                    isdouble = true;
                }
                else if (peek == 'm' || peek == 'M')
                {
                    ReaderRead();
                    suffix = "m";
                    isdecimal = true;
                }
                else if (!isdouble)
                {
                    if (peek == 'u' || peek == 'U')
                    {
                        ReaderRead();
                        suffix = "u";
                        isunsigned = true;
                        peek = (char)ReaderPeek();
                    }

                    if (peek == 'l' || peek == 'L')
                    {
                        ReaderRead();
                        peek = (char)ReaderPeek();
                        islong = true;
                        if (!isunsigned && (peek == 'u' || peek == 'U'))
                        {
                            ReaderRead();
                            suffix = "Lu";
                            isunsigned = true;
                        }
                        else
                        {
                            suffix = isunsigned ? "uL" : "L";
                        }
                    }
                }

                string digit = sb.ToString();
                string stringValue = prefix + digit + suffix;

                if (isfloat)
                {
                    float num;
                    if (float.TryParse(digit, NumberStyles.Any, CultureInfo.InvariantCulture, out num))
                    {
                        return new Token(Tokens.Literal, x, y, stringValue, num);
                    }
                    else
                    {
                        return new Token(Tokens.Literal, x, y, stringValue, 0f);
                    }
                }
                if (isdecimal)
                {
                    decimal num;
                    if (decimal.TryParse(digit, NumberStyles.Any, CultureInfo.InvariantCulture, out num))
                    {
                        return new Token(Tokens.Literal, x, y, stringValue, num);
                    }
                    else
                    {
                        return new Token(Tokens.Literal, x, y, stringValue, 0m);
                    }
                }
                if (isdouble)
                {
                    double num;
                    if (double.TryParse(digit, NumberStyles.Any, CultureInfo.InvariantCulture, out num))
                    {
                        return new Token(Tokens.Literal, x, y, stringValue, num);
                    }
                    else
                    {
                        return new Token(Tokens.Literal, x, y, stringValue, 0d);
                    }
                }
                ulong result;
                if (ishex)
                {
                    if (!ulong.TryParse(digit, NumberStyles.HexNumber, null, out result))
                    {
                        return new Token(Tokens.Literal, x, y, stringValue.ToString(), 0);
                    }
                }
                else
                {
                    if (!ulong.TryParse(digit, NumberStyles.Integer, null, out result))
                    {
                        return new Token(Tokens.Literal, x, y, stringValue.ToString(), 0);
                    }
                }

                if (result > long.MaxValue)
                {
                    islong = true;
                    isunsigned = true;
                }
                else if (result > uint.MaxValue)
                {
                    islong = true;
                }
                else if (islong == false && result > int.MaxValue)
                {
                    isunsigned = true;
                }

                Token token;

                if (islong)
                {
                    if (isunsigned)
                    {
                        ulong num;
                        if (ulong.TryParse(digit, ishex ? NumberStyles.HexNumber : NumberStyles.Number, CultureInfo.InvariantCulture, out num))
                        {
                            token = new Token(Tokens.Literal, x, y, stringValue, num);
                        }
                        else
                        {
                            token = new Token(Tokens.Literal, x, y, stringValue, 0UL);
                        }
                    }
                    else
                    {
                        long num;
                        if (long.TryParse(digit, ishex ? NumberStyles.HexNumber : NumberStyles.Number, CultureInfo.InvariantCulture, out num))
                        {
                            token = new Token(Tokens.Literal, x, y, stringValue, num);
                        }
                        else
                        {
                            token = new Token(Tokens.Literal, x, y, stringValue, 0L);
                        }
                    }
                }
                else
                {
                    if (isunsigned)
                    {
                        uint num;
                        if (uint.TryParse(digit, ishex ? NumberStyles.HexNumber : NumberStyles.Number, CultureInfo.InvariantCulture, out num))
                        {
                            token = new Token(Tokens.Literal, x, y, stringValue, num);
                        }
                        else
                        {
                            token = new Token(Tokens.Literal, x, y, stringValue, (uint)0);
                        }
                    }
                    else
                    {
                        int num;
                        if (int.TryParse(digit, ishex ? NumberStyles.HexNumber : NumberStyles.Number, CultureInfo.InvariantCulture, out num))
                        {
                            token = new Token(Tokens.Literal, x, y, stringValue, num);
                        }
                        else
                        {
                            token = new Token(Tokens.Literal, x, y, stringValue, 0);
                        }
                    }
                }
                token.next = nextToken;
                return token;
            }
        }
        /// <summary>
        /// 读取字符串
        /// </summary>
        /// <returns></returns>
        Token ReadString()
        {
            int x = Col - 1;
            int y = Line;

            sb.Length = 0;
            originalValue.Length = 0;
            originalValue.Append('"');
            bool doneNormally = false;
            int nextChar;
            while ((nextChar = ReaderRead()) != -1)
            {
                char ch = (char)nextChar;

                if (ch == '"')
                {
                    doneNormally = true;
                    originalValue.Append('"');
                    break;
                }

                if (ch == '\\')
                {
                    originalValue.Append('\\');
                    string surrogatePair;
                    originalValue.Append(ReadEscapeSequence(out ch, out surrogatePair));
                    if (surrogatePair != null)
                    {
                        sb.Append(surrogatePair);
                    }
                    else
                    {
                        sb.Append(ch);
                    }
                }
                else if (ch == '\n')
                {
                    HandleLineEnd(ch); 
                    break;
                }
                else
                {
                    originalValue.Append(ch);
                    sb.Append(ch);
                }
            }
            return new Token(Tokens.Literal, x, y, originalValue.ToString(), sb.ToString());
        }

        Token ReadVerbatimString()
        {
            sb.Length = 0;
            originalValue.Length = 0;
            originalValue.Append("@\"");
            int x = Col - 2;
            int y = Line;
            int nextChar;
            while ((nextChar = ReaderRead()) != -1)
            {
                char ch = (char)nextChar;

                if (ch == '"')
                {
                    if (ReaderPeek() != '"')
                    {
                        originalValue.Append('"');
                        break;
                    }
                    originalValue.Append("\"\"");
                    sb.Append('"');
                    ReaderRead();
                }
                else if (HandleLineEnd(ch))
                {
                    sb.Append("\r\n");
                    originalValue.Append("\r\n");
                }
                else
                {
                    sb.Append(ch);
                    originalValue.Append(ch);
                }
            }

            if (nextChar == -1)
            {
            }

            return new Token(Tokens.Literal, x, y, originalValue.ToString(), sb.ToString());
        }

        /// <summary>
        /// 判断是否可能是标识符的一部分
        /// </summary>
        /// <param name="ch"></param>
        /// <returns></returns>
        private static bool IsIdentifierPart(int ch)
        {
            if (ch == '_') return true;
            if (ch == '$') return true;
            if (ch == -1) return false;
            return char.IsLetterOrDigit((char)ch);
        }

        private static bool IsHex(char digit)
        {
            return Char.IsDigit(digit) || ('A' <= digit && digit <= 'F') || ('a' <= digit && digit <= 'f');
        }

        private int GetHexNumber(char digit)
        {
            if (Char.IsDigit(digit))
            {
                return digit - '0';
            }
            if ('A' <= digit && digit <= 'F')
            {
                return digit - 'A' + 0xA;
            }
            if ('a' <= digit && digit <= 'f')
            {
                return digit - 'a' + 0xA;
            }
            return 0;
        }

        private bool WasLineEnd(char ch)
        {
            if (ch == '\r')
            {
                if (reader.Peek() == '\n')
                {
                    ch = (char)reader.Read();
                    ++col;
                }
                else
                {
                    ch = '\n';
                }
            }
            return ch == '\n';
        }

        private bool HandleLineEnd(char ch)
        {
            if (WasLineEnd(ch))
            {
                ++line;
                col = 1;
                return true;
            }
            return false;
        }

        private void SkipToEndOfLine()
        {
            int nextChar;
            while ((nextChar = reader.Read()) != -1)
            {
                if (HandleLineEnd((char)nextChar))
                {
                    break;
                }
            }
        }

        private string ReadToEndOfLine()
        {
            sb.Length = 0;
            int nextChar;
            while ((nextChar = reader.Read()) != -1)
            {
                char ch = (char)nextChar;
                if (HandleLineEnd(ch))
                {
                    return sb.ToString();
                }

                sb.Append(ch);
            }
            string retStr = sb.ToString();
            col += retStr.Length;
            return retStr;
        }

        Token ReadChar()
        {
            int x = Col - 1;
            int y = Line;
            int nextChar = ReaderRead();
            if (nextChar == -1)
            {
                return null;
            }
            char ch = (char)nextChar;
            char chValue = ch;
            string escapeSequence = String.Empty;
            if (ch == '\\')
            {
                string surrogatePair;
                escapeSequence = ReadEscapeSequence(out chValue, out surrogatePair);
                if (surrogatePair != null)
                {
                }
            }
            unchecked
            {
                if ((char)ReaderRead() != '\'')
                {
                }
            }
            return new Token(Tokens.Literal, x, y, "'" + ch + escapeSequence + "'", chValue);
        }

        #region Escape
        char[] escapeSequenceBuffer = new char[12];
        string ReadEscapeSequence(out char ch, out string surrogatePair)
        {
            surrogatePair = null;

            int nextChar = ReaderRead();
            if (nextChar == -1)
            {
                ch = '\0';
                return String.Empty;
            }
            int number;
            char c = (char)nextChar;
            int curPos = 1;
            escapeSequenceBuffer[0] = c;
            switch (c)
            {
                case '\'':
                    ch = '\'';
                    break;
                case '\"':
                    ch = '\"';
                    break;
                case '\\':
                    ch = '\\';
                    break;
                case '0':
                    ch = '\0';
                    break;
                case 'a':
                    ch = '\a';
                    break;
                case 'b':
                    ch = '\b';
                    break;
                case 'f':
                    ch = '\f';
                    break;
                case 'n':
                    ch = '\n';
                    break;
                case 'r':
                    ch = '\r';
                    break;
                case 't':
                    ch = '\t';
                    break;
                case 'v':
                    ch = '\v';
                    break;
                case 'u':
                case 'x':
                    // 16 bit unicode character
                    c = (char)ReaderRead();
                    number = GetHexNumber(c);
                    escapeSequenceBuffer[curPos++] = c;

                    if (number < 0)
                    {
                    }
                    for (int i = 0; i < 3; ++i)
                    {
                        if (IsHex((char)ReaderPeek()))
                        {
                            c = (char)ReaderRead();
                            int idx = GetHexNumber(c);
                            escapeSequenceBuffer[curPos++] = c;
                            number = 16 * number + idx;
                        }
                        else
                        {
                            break;
                        }
                    }
                    ch = (char)number;
                    break;
                case 'U':
                    // 32 bit unicode character
                    number = 0;
                    for (int i = 0; i < 8; ++i)
                    {
                        if (IsHex((char)ReaderPeek()))
                        {
                            c = (char)ReaderRead();
                            int idx = GetHexNumber(c);
                            escapeSequenceBuffer[curPos++] = c;
                            number = 16 * number + idx;
                        }
                        else
                        {
                            break;
                        }
                    }
                    if (number > 0xffff)
                    {
                        ch = '\0';
                        surrogatePair = char.ConvertFromUtf32(number);
                    }
                    else
                    {
                        ch = (char)number;
                    }
                    break;
                default:
                    ch = '\0';
                    break;
            }
            return new String(escapeSequenceBuffer, 0, curPos);
        } 
        #endregion

        #region IDisposable Members

        public void Complete()
        {
            if (reader != null)
                reader.Dispose();
            sb = null;
            originalValue = null;
            identBuffer = null;
        }

        #endregion
    }
}